<?php
/**
 * 元素描述抽象类
 *
 * @version $Id: IAbstractElement.php 312 2008-04-23 16:29:38Z iwind $
 * @package util.sqlmap.element2
 */
abstract class IAbstractElement extends IObject implements IElement {
	/**
	 * XML节点
	 *
	 * @var IXMLNode
	 */
	private $node;
	/**
	 * Enter description here...
	 *
	 * @var IDbStatement
	 */
	private $statement;
	
	/**
	 * Enter description here...
	 *
	 * @var IDbDriver
	 */
	private $driver;
	
	/**
	 * Enter description here...
	 *
	 * @var array
	 */
	private $elementMap = array();
	
	/**
	 * 命名空间
	 *
	 * @var string
	 */
	private $namespace;
	
	/**
	 * 替换列表
	 *
	 * @var array
	 */
	private $replaceMap;
	
	/**
	 * 取得当前节点
	 *
	 * @return IXmlNode
	 */
	public function getNode() {
		return $this->node;
	}
	
	/**
	 * 设置IXmlNode节点
	 *
	 * @param IXmlNode $node IXmlNode节点
	 */
	public function setNode(IXmlNode $node) {
		$this->node = $node;
	}
	
	/**
	 * 取得数据库驱动
	 *
	 * @return IDbDriver
	 */
	public function getDriver() {
		return $this->driver;
	}
	
	/**
	 * 设置数据库驱动
	 *
	 * @param IDbDriver $driver 数据库驱动
	 */	
	public function setDriver($driver) {
		$this->driver = $driver;
	}
	
	/**
	 * 设置所在的元素解析器中所含的元素表(element map)
	 *
	 * @param array $elementMap 元素表
	 */	
	public function setElementMap(&$elementMap) {
		$this->elementMap = &$elementMap;
	}
	
	/**
	 * 取得所在的元素解析器中所含的元素表
	 *
	 * @return array
	 */
	public function getElementMap() {
		return $this->elementMap;
	}
	
	/**
	 * 取得SQL语句
	 *
	 * @return string
	 */
	public function getSql() {
		$sql = $this->node->getValue();
		if (is_array($this->replaceMap)) {
			$sql = strtr($sql, $this->replaceMap);
		}
		return $sql;	
	}
	
	/**
	 * 从当前元素中取到语句对象
	 * 
	 * @return IDbStatement
	 */
	public function toStatement() {
		$sql = $this->getSql();
		return $this->driver->prepare($sql, array());
	}
	
	/**
	 * 执行
	 * 
	 */	
	public function execute() {
		$this->statement->execute();
	}
	
	/**
	 * 得到标签名
	 *
	 * @return string
	 */	
	public function getTagName() {
		return $this->getNode()->getName();
	}
	
	/**
	 * 取得当前元素对应的SQL ID
	 *
	 * @return string
	 */
	public function getSqlId() {
		$sqlId = $this->getNode()->getAttribute("id");
		if ($this->namespace != null) {
			$sqlId = $this->namespace . "." . $sqlId;
		}
		return $sqlId;
	}
	
	/**
	 * 设置命名空间
	 * 
	 * 定义在每个SQL XML文件中的sqlMap元素上
	 *
	 * @param string $namespace 命名空间
	 */
	public function setNamespace($namespace) {
		$this->namespace = $namespace;
	}
	
	/**
	 * 取得命名空间
	 *
	 * @return string
	 */
	public function getNamespace() {
		return $this->namespace;
	}
	
	/**
	 * 取得元素中的parameterMap属性对应的元素
	 *
	 * @return IParameterMapElement
	 * @throws ISqlMapException
	 */
	public function getParameterMapElement() {
		$parameterMapName = trim($this->getNode()->getAttribute("parameterMap"));
		if ($parameterMapName != "") {
			$namespace = $this->getNamespace();
			if ($namespace != null) {
				$parameterMapName = $namespace . "." . $parameterMapName;
			}
			$elements = $this->getElementMap();
			if (isset($elements[$parameterMapName])) {
				return $elements[$parameterMapName];
			}
			else {
				throw new ISqlMapException("can't find parameter map for '{$parameterMapName}' in SQL " . $this->getSqlId());
			}
		}
		return null;
	}
	
	/**
	 * 取得占位参数
	 * 
	 * 得到 array 
	 *     (
	 * 		"namedParams" => array(name1=>array(position1, position2), name2=>array(...), 
	 * 		"positionalParams" => array(position1, position2, ...)
	 *     );
	 *
	 * @return array
	 */
	public function getPlaceHolder() {
		return $this->parseSql($this->getSql());
	}
	
	/**
	 * 设置替换列表
	 *
	 * @param array $replaceMap 替换列表（关联数组）
	 */
	public function setReplaceMap($replaceMap) {
		if (is_array($replaceMap)) {
			$newMap = array();
			foreach ($replaceMap as $key=>$value) {
				$newMap["#" . $key . "#"] = $value;
			}
			$this->replaceMap = $newMap; 
		}
	}
	
	/**
	 * 分析SQL语句
	 * 
	 * 得到 array 
	 *     (
	 * 		"namedParams" => array(name1=>array(position1, position2), name2=>array(...), 
	 * 		"positionalParams" => array(position1, position2, ...)
	 *     );
	 *
	 * @param string $sql SQL语句
	 * @return array
	 */
	private function parseSql($sql) {
		$length = strlen($sql);
		$namedParams = array();// array(name1=>array(position1, position2), name2=>array(...));
		$positionalParams = array();//array(position1, position2, ...)
		$lastQuote = null;
		for ($i=0; $i<$length; $i++) {
			$char = $sql{$i};
			if ($char == "'" || $char == "\"") {
				if ($lastQuote != null) {
					if ($lastQuote == $char) {
						$lastQuote = null;
					}
					else {
						continue;
					}
				}
				else {
					$lastQuote = $char;
				}
			}
			else {
				if ($lastQuote == null) {
					if ($char == "?") {
						$positionalParams[] = $i;
					}
					else if ($char == ":") {
						$nameStart = $i;
						$name = "";
						while(true) {
							$nameStart ++;
							if ($nameStart > $length - 1) {
								break;
							}
							$nameChar = $sql{$nameStart};
							$ord = ord($nameChar); //48-57 97-122 65-90 95
							if ($ord == 95 || ($ord >= 48 && $ord <=57) || ($ord >= 97 && $ord <=122) || ($ord >= 65 && $ord <=90)) {
								$name .= $nameChar;
							}
							else {
								break;
							}
						}
						if ($name != "") {
							if (!isset($namedParams[$name])) {
								$namedParams[$name] = array();	
							}
							$namedParams[$name][] = $i;
						}
					}
				}
			}
		}
		return compact("namedParams", "positionalParams");
	}
}

?>